﻿// The MIT License (MIT)
// Copyright (c) 2013 Stylianos Dritsas
//
// Permission is hereby granted, free of charge, to any person obtaining a 
// copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the 
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included 
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.

//-- System Imports
//--
using System;
using System.Collections.Generic;

//-- Library Imports
//--
using Bentley.Geometry;
using Bentley.GenerativeComponents;
using Bentley.GenerativeComponents.MicroStation;
using Bentley.GenerativeComponents.GCScript;
using Bentley.Interop.MicroStationDGN;

namespace Bentley.GenerativeComponents.Features.Specific 
{
  public class DemoSolver : Sutd.ManagedFeature
  {
    [Update]
    public bool Solver(
      FeatureUpdateContext updateContext,
      [ParentModel] ICurve Curve,
      double Chord,
      int Solver )
    {
      //-- Initialize & Input Validation
      //--
      Regenerate( );
      if( Curve == null ) return false;

      //-- Select Search Method
      //--
      Search search = MarchSearch;
      if( Solver == 1 ) search = BranchSearch; else
      if( Solver == 2 ) search = NewtonSearch;

      //-- Compute Chords
      //--
      var curve = Curve.com_bsplineCurve;
      var point = curve.EvaluatePoint( 0.0 );
      var param = 0.0;
      do
      {
        AddPoint( point );
      }
      while( search( curve, Chord, ref param, ref point ) );

      return true; 
    }

    #region -- Solvers --------------------------------------------------------

    private delegate bool Search( BsplineCurve curve, double chord, ref double param, ref Point3d result, double tolerance = 0.0001 );

    #region -- March Solver ---------------------------------------------------

    //-- Brute force-ish
    //--
    private bool MarchSearch(
      BsplineCurve curve,
      double chord,
      ref double param,
      ref Point3d result,
      double tolerance = 0.0001 )
    {
      //-- Evaluate Start Point
      //--
      var start = curve.EvaluatePoint( param );

      //-- Make step size assumption
      //--
      var step = 0.1;

      while( ( param >= 0.0 ) &&
             ( param <= 1.0 ) )
      {
        //-- Next Point
        //--
        param += step;
        result = curve.EvaluatePoint( param );

        //-- Found within Tolerance?
        //--
        var distance = MSApp.Point3dDistance( ref start, ref result );
        if( Math.Abs( distance - chord ) < tolerance ) return true;

        //#debug
        {
          AddLine( start, result );
        }
        //#enddebug

        //-- Overshoot -> Switch Direction + Half Step
        //--
        if( ( ( step > 0 ) && ( distance > chord ) ) ||
            ( ( step < 0 ) && ( distance < chord ) ) ) 
          step *= -0.5;
      }

      return false;
    }

    #endregion

    #region -- Branch Solver --------------------------------------------------

    //-- Biforcation/Branching Solver
    //-- 
    //--
    private bool BranchSearch(
      BsplineCurve curve, 
      double chord, 
      ref double param,
      ref Point3d result,
      double tolerance = 0.0001 )
    {
      //-- Evaluate Start Point
      //--
      var start = curve.EvaluatePoint( param );

      //-- Search Between [Curve.start, Curve.Max]
      //--
      var min = param;
      var max = 1.0;
      while( ( max - min ) > tolerance )
      {
        //-- Evaluate Mid-Point
        //--
        param = 0.5 * ( min + max );
        result = curve.EvaluatePoint( param );

        //-- Found within Tolerance?
        //--
        var distance = MSApp.Point3dDistance( ref start, ref result );
        if( Math.Abs( distance - chord ) < tolerance ) return true;

        //#debug
        {
          AddLine( start, result );
        }
        //#enddebug

        //-- Select Half-Span
        //--
        if( distance < chord )
          min = param;
        else
          max = param;
      }

      //-- End Point Reached
      //--
      return false;
    }

    #endregion

    #region -- Newton Raphson Search ------------------------------------------

    private bool NewtonSearch(
      BsplineCurve curve,
      double chord,
      ref double param,
      ref Point3d result,
      double tolerance = 0.0001 )
    {
      //-- Start Point
      //--
      var start = curve.EvaluatePoint( param );

      //-- Initial Guess: Blind Forward
      //--
      param += 0.1;

      while( ( param >= 0.0 ) && 
             ( param <= 1.0 ) )
      {
        //-- Evaluate Point
        //--
        result = curve.EvaluatePoint( param );

        var distance = MSApp.Point3dDistance( ref start, ref result ) - chord;
        if( Math.Abs( distance ) < tolerance ) return true;

        //#debug
        {
          AddLine( start, result );
        }
        //#enddebug

        //-- Newton Raphson Improved Guess
        //-- f'(t) = (f(t+h) - f(t)) / h
        var point = curve.EvaluatePoint( param + tolerance );
        var delta = ( MSApp.Point3dDistance( ref start, ref point ) - ( distance + chord ) ) / tolerance;
        param -= distance / delta;
      }

      return false;
    }

    #endregion

    #endregion
  }
}
